var fs=require("fs");
var gd=require("gd");
var proc=require("process");

var Socket = require("socket").Socket;
var Buffer = require("binary").Buffer;

var exif_reader = require("exif").Reader;

exports.add=[{
	_type:"functions",
	_section:"Files",

	/**
	 * Function: Files.uploads_folder
	 *
	 * Returns:
	 *		typeof String	- path to uploads
	 */
	uploads_folder: function(diskpath) {
		var uf=this.site.paths.uploads_subfolder;
		if (!uf) {
			uf=this.system.paths.uploads_subfolder;
			if (uf.constructor===Array) uf=uf[0];
		}
		if (diskpath) {
			return this.site.paths.html+uf;
		} else {
			return "/"+uf;
		}
	},

	/**
	 * Function: Files.file_folder_path
	 *		Calculates URL of filesystem path to file_folder
	 *
	 * Parameters:
	 *		ff_id typeof Integer		- File group id, an ID from Tfile_folders table
	 *		is_diskpath typeof Boolean	- Return disk path, not URL
	 *
	 * Returns:
	 *		typeof String - Filesystem path to file_folder
	 */
	file_folder_path: function(ff_id,is_diskpath,force_use_sql) {
		var ffs=this.site.file_folders_server;
		if (ffs && !force_use_sql) {
			if (!ffs.socket) {
				try {
					ffs.socket=new Socket(Socket.PF_INET, Socket.SOCK_STREAM, Socket.IPPROTO_TCP);
					ffs.socket.connect(ffs.address,ffs.port);
				} catch(e) {
					ffs.socket=undefined;
					return this.F("Files","file_folder_path",ff_id,is_diskpath,1);
				}
			}
			ffs.socket.send((is_diskpath?"1":"0")+"/"+ff_id+",");
			var buffer=ffs.socket.receive(5000);
			if (buffer[buffer.length-1]!=44) { // ,
				return this.F("Files","file_folder_path",ff_id,is_diskpath,1);
			}
			return buffer.toString("ASCII",0,buffer.length-1);
		}

		var cd=this._cache.file_folder_dirs;
		var cm=this._cache.file_folder_models;
		if (!cd) cd=this._cache.file_folder_dirs={};
		if (!cm) cm=this._cache.file_folder_models={};
		if (cd[is_diskpath+"-"+ff_id]) return cd[ff_id];

		var ret="";
		while (ff_id && ff_id!="0") {
			var grp=this._cache.file_folder_models[ff_id];
			if (!grp) {
				grp=this._cache.file_folder_models[ff_id]=this.site.models.FileFolder.Get(ff_id);
			}
			ret=(grp.code||grp.id)+"/"+ret;
			ff_id=grp.parent_id;
		}
		cd[is_diskpath+"-"+ff_id]=this.F("Files","uploads_folder",is_diskpath)+ret;
		return cd[is_diskpath+"-"+ff_id];
	},

	/**
	 * Function: Files.init_files
	 */
	init_files: function(array,colname,colname2) {
		for (var i=0;i<array.length;i++) this.F("Files","init_file",array[i],colname,colname2);
	},

	/*
	 * Function: Files.init_file
	 *		Fills object, creating a valid URL to file
	 *
	 * Parameters:
	 *		model typeof LNModel	- an object
	 *		colname					- file prefix in "model" object
	 *		colname2				- (optional, defaults to colname+"_id")
	 *
	 * Returns:
	 *		None
	 *		* a colname+"_name" value inside model[] object is filled.
	 */
	init_file: function(model,colname,colname2) {
		if (!colname2) colname2=colname+"_id";
		if (!model[colname2]) {
			model[colname+"_name_or_filename"]="";
			model[colname+"_name"]="";
			return;
		}
		model[colname+"_url"]=this.F("Files","file_folder_path",model[colname+"_file_folder_id"])+model[colname2]+"."+model[colname+"_ext"];
		model[colname+"_name_or_filename"]=model[colname+"_name"]||model[colname+"_orig_filename"];
		if (model[colname+"_name"]==undefined || model[colname+"_name"]==null) model[colname+"_name"]="";
	},

	/**
	 * Function: Files.user_get_autoscale_policy
	 */
	user_get_autoscale_policy: function(mode) {
		var t=this;
		var x,y;
		function step(role) {
			var opt=t.F("System","get_option",["autoscale",role,mode]);
			if (!opt) return 1;
			if (opt[0]==-1) return 0;
			if (x==undefined || x==null) {x=opt[0];y=opt[1];return 1;}
			if (opt[0]>x) x=opt[0];
			if (opt[1]>y) y=opt[1];
			return 1;
		}
		if (!step("no_roles")) return {};
		if (this.roles) {
			for (var role in this.roles) if (!step(role)) return {};
		}
		return {x:x,y:y};
	},

	/*
	 * Function: Files.guess_mime
	 * Guesses mime type.
	 */
	guess_mime: function(ext) {
		if (ext.match(/^jpe?g$/i)) return "image/jpeg";
		if (ext.match(/^png$/i)) return "image/png";
		if (ext.match(/^gif$/i)) return "image/gif";
		if (ext.match(/^css$/i)) return "text/css";
		if (ext.match(/^js$/i)) return "text/javascript";
		return "application/octet-stream";
	},

	/*
	 * Function: Files.guess_file_type
	 *		Guesses file type. Right now works only by extention.
	 *
	 * Parameters:
	 *		ext - File extention
	 *		filedata - File contents
	 *
	 * Returns:
	 *		* 1 for image
	 *		* 2 for video
	 *		* 3 for audio
	 *		* 4 for others
	 */
	guess_file_type: function(ext,filedata) {
		if (ext.match(/^(png|gif|jpg|jpeg)$/)) return 1;
		if (ext.match(/^(avi|flv|mpg|mkv|mp4)$/)) return 2;
		if (ext.match(/^(mp3)$/)) return 3;
		return 4;
	},
	/*
	 * Function: Files.mkdir_rec
	 *		Makes a directory recursively
	 *
	 * Parameters:
			path - a directory or file name to create
			isFile - true/false, is name in "path" variable a filename or a foldername

		Returns:
			none
	*/
	mkdir_rec: function (path,isFile) {
		path=path.replace(/^\//,"").replace(/\/\.\./g,"");
		var arr=path.split(/\//);
		var max=isFile?arr.length-1:arr.length;
		var basepath="/";
		for (var i=0;i<max;i++) {
			basepath+=arr[i]+"/";
			var d=new fs.Directory(basepath);
			if (!d.isDirectory())
				try {
					d.create();
				} catch(e) {
					throw e+" (path="+basepath+")\n";
				}
		}
	},
	/*
	 * Function: Files.ajajform_file_folder_value
	 */
	ajajform_file_folder_value: function (space,file_folder_id) {
		var r=space.site.sql.execute_and_fetch_one("file_folders/get",{file_folder_id:file_folder_id});
		r.files=space.site.sql.execute_and_fetch("files/list",{file_folder_id:file_folder_id});
		var path=space.functions.file_folder_path(space,file_folder_id);
		for (var i=0,s=r.files.length;i<s;i++) {
			var file=r.files[i];
			file.url=path+file.id+"."+file.ext;
			file.name_or_filename=file.name||file.orig_filename;
			if (file.name==undefined || file.name==null) file.name="";
		}
		return r;
	},

	/*
		Function: functions.gd_image_type_by_ext

		Parameters:
			ext - File extention

		Returns:
			GD-format image type
	*/
	gd_image_type_by_ext: function(ext) {
		switch (ext) {
			case 'png':
				return gd.Image.PNG;
			case 'jpg':
			case 'jpeg':
				return gd.Image.JPEG;
			case 'jpeg':
				return gd.Image.JPEG;
			case 'gif':
				return gd.Image.GIF;
			default:
				throw("Unknown file ext='"+ext+"'");
		}
	},
	/**
	 *	Function: Files.rotate_file90
	 *		Rotates a file in Tfiles by 90 degrees, making a new entry in Tfiles and deleting old one
	 *		Filesystem is changed
	 *		Tfiles is changed
	 *
	 *	Parameters:
	 *		file_id typeof Integer		- id from Tfiles
	 *		val typeof Integer			- direction (1 or -1)
	 *	Returns:
	 *		typeof Integer				- id from Tfiles of new (rotated) file
	 */
	rotate_file90: function(file_id,val) {
		var file1=this.site.models.File.Get(file_id);
		var file2=this.site.models.File.Create();
		file2.CopyLocalesFromModel(file1);
		file2.type_id=file1.type_id;
		file2.file_folder_id=file1.file_folder_id;
		file2.user_cr_id=file1.user_cr_id;
		file2.orig_filename=file1.orig_filename;
		file2.ext=file1.ext;
		file2.SaveAll();

		var path=this.F("Files","file_folder_path",file1.file_folder_id,1);
		var srcfile=path+file1.id+"."+file1.ext;
		var dstfile=path+file2.id+"."+file2.ext;
		this.F("Files","rotate_fs_image90",srcfile,dstfile,val);
		file1.Delete();
		this.F("Files","file_update_stats",file2.id);
		return file2.id;
	},

	/**
	 *	Function: Files.rotate_fs_image90
	 *		Rotates image on hard drive (does not do anything to database)
	 *
	 *	Parameters:
	 *		srcfile typeof String		- source file
	 *		dstfile typeof String		- destination file
	 *		val typeof Integer			- direction (-1 or 1)
	 */
	rotate_fs_image90: function(srcfile,dstfile,val) {
		if (val!=1 && val!=-1) throw("Can not rotate");
		var image_type=this.F("Files","gd_image_type_by_ext",srcfile.match(/\.(\w+)$/)[1]);

//		TODO: for jpeg use jhead or something that will not resample
//		var jt="jhead -q -autorot "+filename;
//		var r=(new Process()).system(jt);
//		if (r) throw "Process().system(\"jt\") returned "+r;

		var image=new gd.Image(image_type,srcfile);
		var ow=image.sx();
		var oh=image.sy();
		var dstimage=new gd.Image(gd.Image.TRUECOLOR,oh,ow);
		dstimage.copyRotated(image,oh/2,ow/2,0,0,ow,oh,90*val);
		dstimage.save(image_type,dstfile);
	},

	/**
	 *	Function: Files.rotate_file_exif
	 *		Rotates file if it is image and needs rotating
	 *		Changes filesystem
	 *		Does not change database
	 *
	 *	Paramters:
	 *		file_id typeof Integer		- id from Tfiles
	 *
	 *	Returns:
	 *		EXIF.Orientation if rotation was done
	 */
	rotate_file_exif: function(file_id) {
		var file=this.site.models.File.Get(file_id);
		var path=this.F("Files","file_folder_path",file.file_folder_id,1);
		var filename=path+file.id+"."+file.ext;
		var filename2=path+file.id+"-tmp."+file.ext;

		var filehandle=new fs.File(filename);
		filehandle.open("r");
		var image_data=filehandle.read();
		filehandle.close();

		var reader = new exif_reader(image_data);
		image_data=undefined;
		var tags=reader.getTags();
		if (!tags.Orientation) return;

		var val;
		if (tags.Orientation==8) val=3;
		if (tags.Orientation==3) val=2;
		if (tags.Orientation==6) val=1;
		if (!val) return;
		this.F("Files","rotate_fs_image90",filename,filename2,val);

		var pfiles=new fs.Directory(path).listFiles();
		var re=new RegExp("^"+file_id+"-preview");
		pfiles.forEach(function(pfile) {
			if (pfile.match(re)) (new fs.File(path+pfile)).remove();
		});
		this.F("Files","file_update_stats",file.id);
		return tags.Orientation;
	},

	/*
	 * Function: Files.scale_image
	 *		Scales image
	 *
	 * Parameters:
	 *		object typeof Object				- Object, containing following:
	 *		object.srcfile typeof String		- source file
	 *		object.dstfile tpyoef String		- destination file
	 *		object.w typeof Integer				- new width
	 *		object.h typeof Integer				- new height
	 *		object.mo typeof String				- modification rule, "", "C", or some else
	 *		object.moval typeof Integer			- modification rule add
	 *		object.exact						- TODO not used
	 *		object.ext typeof String			- file extention
	 *		object.trim typeof Boolean			- do a trim
			object.upscale typeof Boolean		- upscale an image if it is smaller than resulting
	 *
	 *	Returns:
	 *		none
	 *		* FIlesystem is changed, new file is created
	 */
	scale_image: function(object) {
		var srcfile=object.srcfile,dstfile=object.dstfile,w=object.w,h=object.h,mo=object.mo,moval=object.moval,exact=object.exact,ext=object.ext;
//		throw object;
		var image_type=this.F("Files","gd_image_type_by_ext",ext);
		var image;
		try {
			if (object.trim) {
				var srcfile1=srcfile.replace(/\.(\w+)$/,function(p,p1) { return "-TRIM."+p1;});
				var test=new fs.File(srcfile1);
				if (!test.exists()) {
					var p=new proc.Process();
					p.system("convert -virtual-pixel edge -fuzz 3% -trim "+srcfile+" "+srcfile1);
					if (this.site.callbacks && this.site.callbacks.after_trim) this.site.callbacks.after_trim(this,srcfile1);
				}
				image=new gd.Image(image_type,srcfile1);
			} else {
				image=new gd.Image(image_type,srcfile);
			}
		} catch(e) {
			throw {"function":"Files.scale_image",error:e,srcfile:srcfile};
		}
		var ow=image.sx();
		var oh=image.sy();
		var dstimage;
		switch (mo) {
			case "":
				if (ow>w || oh>h || object.upscale) {
					var sc=w/ow;
					var nh,nw;
					if (sc<h/oh) {
						nw=w;
						nh=Math.floor(oh*sc);
						if (nh>h) nh=h;
					} else {
						sc=h/oh;
						nw=Math.floor(ow*sc);
						if (nw>w) nw=w;
						nh=h;
					}
					dstimage=new gd.Image(gd.Image.TRUECOLOR,nw,nh);
					dstimage.alphaBlending(false);
					dstimage.saveAlpha(true);
					dstimage.copyResampled(image,0,0,0,0,nw,nh,ow,oh);
				} else {
					dstimage=image;
					dstimage.alphaBlending(false);
					dstimage.saveAlpha(true);
				}
				break;
/*		case "X":
			if ($ow>$w) {
				my $nh=int($oh*$w/$ow);
				$m->Scale(width=>$w,height=>$nh);
			}
			break;
		case "Y":
			if ($oh>$h) {
				my $nw=int($ow*$h/$oh);
				$m->Scale(width=>$nw,height=>$h);
			}
			break;*/
			case "C":
/*			if (!exact && $srcfile=~/^(.*\/)([^\/]+)$/) {
				if (open IN,"$1.$2.focus") {
					my $f=<IN>;
					chomp $f;
					close IN;
					if ($ow/$w>$oh/$h) {
						$moval=  0 if $f==1 || $f==4 || $f==7;
						$moval=100 if $f==3 || $f==6 || $f==9;
					} else {
						$moval=  0 if $f==1 || $f==2 || $f==3;
						$moval=100 if $f==7 || $f==8 || $f==9;
					}
				}
			}*/
/*			if (ow/w==oh/h) {
			} elsif (ow/w>oh/h) {
				// Width is greater then Height
				var cw=Math.floor(oh*w/h);
				var mo=Math.floor(moval*(ow-cw)/100);
				//$m->Crop(geometry=>$cw."x".$oh."+$mo+0");
			} else {
				var ch=Math.floor(ow*h/w);
				var mo=Math.floor(moval*(oh-ch)/100);
				//$m->Crop(geometry=>$ow."x".$ch."+0+$mo");
			}*/
				if (ow>w || oh>h || object.upscale) {
					var ooh,oow;
					dstimage=new gd.Image(gd.Image.TRUECOLOR,w,h);
					if (ow/w > oh/h) {
						// Width is greater then Height
						ooh=oh;
						oow=Math.floor(oh*w/h);
					} else {
						oow=ow;
						ooh=Math.floor(ow*h/w);
					}
					dstimage.alphaBlending(false);
					dstimage.saveAlpha(true);
					dstimage.copyResampled(image,0,0,(ow-oow)/2,(oh-ooh)/2,w,h,oow,ooh);
	/*				$m->Scale(width=>$w,height=>$h);
					if ($m->Get("width")!=$w || $m->Get("height")!=$h) {
						warn "[4] $dstfile Current geomtery is ".$m->Get("width")."x".$m->Get("height").", w/h=$w/$h\n";
						$m->Crop(geometry=>$w."x".$h."+0+0");
						warn "[5] Current geomtery is ".$m->Get("width")."x".$m->Get("height")."\n";
					}*/
				} else {
					dstimage=image;
				}
				break;
			default:
				throw("Unknown mo "+mo);
		}
		if (!dstimage) {
			throw "scale_image(srcfile="+srcfile+",dstfile="+dstfile+",w="+w+",h="+h+",mo="+mo+",moval="+moval+",exact="+exact+",ext="+ext+" -- dstfile is null";
		}
		dstimage.save(image_type,dstfile);
	},

	/**
	 * Funciton: Files.find_file_in_paths
	 */
	find_file_in_paths: function(file,path_type) {
		var basepath=this.site.paths[path_type];
		var testfile=new fs.File(basepath+file);
		var arr=this.system.paths[path_type];
		var i=0;
		while (!testfile.isFile()) {
			if (i==arr.length) return undefined;
			basepath=arr[i];
			testfile=new fs.File(basepath+file);
			i++;
		}
		return testfile.toString();
	},
	/**
	 * Funciton: Files.guess_icon
	 */
	guess_icon: function(ext) {
		var basepath1=this.site.paths.html;
		var basepath2=this.system.paths.html;
		var icon="/shared/icons/filebrowser/"+ext+".png";
		if (!this.F("Files","find_file_in_paths",icon,"html")) {
			icon="/shared/icons/filebrowser/default.png";
		}
		return icon;
	},

	/*
	 * Function: Files.file_update_stats
	 *		Updates file stats, that is - width, height, filesize, seconds (if available)
	 *
	 * Parameters:
	 *		file_id - id in Tfiles
	 *
	 * Returns:
	 *		none
	 */
	file_update_stats: function(file_id) {
		var href={
			file_id: file_id,
			width: null,
			height: null,
			filesize: null,
			seconds: null
		};
		var file=this.site.models.File.Get(file_id);
		var filefolder=this.F("Files","file_folder_path",file.file_folder_id,1);
		var filename=filefolder+file.id+"."+file.ext;
		var fsf=new fs.File(filename);
		if (fsf.exists()) {
			href.filesize=fsf.stat().size;
//			throw space.Dumper([filename,href.filesize]);
			switch (1*file.type_id) {
				case 1: // Image
					var image_type=this.F("Files","gd_image_type_by_ext",file.ext);
					try {
						var image=new gd.Image(image_type,filename);
					} catch(e) {
						throw "Can not open file, filename="+filename;
					}
					href.width=image.sx();
					href.height=image.sy();
					break;
				case 2: // Video
					var p=new proc.Process();
					var dir=new fs.Directory(filefolder);
					var files=dir.listFiles();
					var file_id1=file_id+"";
					for (var i=0;i<files.length;i++) {
						if (files[i]==file_id1+".jpg" || files[i].substr(0,file_id1.length+8)==file_id1+"-preview") {
							var ftmp=new fs.File(filefolder+files[i]);
							ftmp.remove();
						}
					}
					//avconv -loglevel error -itsoffset -2 -i f/6/8/158/398/116609.mp4 -vcodec mjpeg -vframes 1 -an -f rawvideo test.jpg 
					var lines=p.exec("avconv -loglevel error -itsoffset -2 -i "+filename+" -vcodec mjpeg -vframes 1 -an -f rawvideo "+filefolder+file_id+".jpg");
					var image_type=this.F("Files","gd_image_type_by_ext","jpg");
					try {
						var image=new gd.Image(image_type,filefolder+file_id+".jpg");
					} catch(e) {
						throw "Can not open file, filename="+filefolder+file_id+".jpg";
					}
					href.width=image.sx();
					href.height=image.sy();
					break;
				case 3: // Sound
					break;
				case 4: // Other
					break;
			}
		}
		this.site.sql.execute("files/set_stats",href);
	},
	/*
		Function: Files.all_files_update_stats

		Updates stats of all files

		Parameters:

		Returns:
			none
	*/
	all_files_update_stats: function(space) {
		var t=this;
		this.site.sql.execute_and_fetch_single("select id from Tfiles").forEach(function(x) {
			t.F("Files","file_update_stats",x.id);
		});
	},

	/*
		Function: Files.user_upload_file

		Parameters:
			options - a hash of:
			options.inputname					- An <input type="file"> name
			options.ff_id						- An ID from Tfile_folders table
			options.fg_code						- (OR)
			options.fg_name						- (OR)
			options.uid							- (optional, defaults to this.uid) an ID from Tusers table
			options.filter_func					- function
			options.downscale_policy			- a policy to downscale (too long, will describe later TODO)
			options.name						- A locale name
			options.l10ns typeof Object			- A locale names
			options.existingfile typeof String	- optional, existing file name
			options.trim typeof Boolean			-
		Returns:
			New file ID, from Tfiles table
	*/
	user_upload_file: function(options) {

		var ff_id=options.ff_id;//functions.get_file_folder_id(space,options,"fg");
		if (!ff_id) throw "No file folder found!";
		if (!options.inputname && !options.existingfile) throw "No options.inputname";
		var uid=options.uid||this.uid;
		var downscale_policy=options.downscale_policy;

		var fileinfo;
		if (options.existingfile) {
			fileinfo={originalName:options.existingfile};
		} else {
			fileinfo=this.req.files[options.inputname];
		}
		
		var path=this.F("Files","file_folder_path",ff_id,1);
//		var basepath=space.site.paths.html;
		this.F("Files","mkdir_rec",path);
		
		if (!fileinfo) return undefined;
		if (!fileinfo.originalName) return undefined;
		var arr1=fileinfo.originalName.match(/\.([a-zA-Z0-9_\-]{1,8})$/);
		if (!arr1) throw "No extention of file "+this.Dumper(fileinfo);
		var ext=arr1[1].toLowerCase();
		var filename=fileinfo.originalName.toLowerCase();
		filename=filename.replace(/^.*[\/\\\|]/);
		filename=filename.replace(/\.([a-zA-Z0-9_\-]{1,8})$/,"");
		var filedata=fileinfo.data;
		var type_id=this.F("Files","guess_file_type",ext,filedata);
/*		if (options.filter_func) {
			if (!options.filter_func(this,{uid:uid,original:fileinfo,type_id:type_id})) return undefined;
		} else {*/
			if (type_id==1) {
/*				if (!downscale_policy) {
					downscale_policy=space.functions.user_get_autoscale_policy(space,"max");
					if (space.fields.downscale) {
						if (downscale_policy && downscale_policy.x) {
							var max_x=1*space.fields.max_x;
							var max_y=1*space.fields.max_y;
							if (max_x<downscale_policy.x && max_x>0) downscale_policy.x=max_x;
							if (max_y<downscale_policy.y && max_y>0) downscale_policy.y=max_y;
							throw space.Dumper([downscale_policy,space.fields]);
						} else {*/
							downscale_policy={x:this.fields.max_x,y:this.fields.max_y};
/*						}
					}
				}*/
			} else if (!this.roles.files) {
				throw "Not image";
			}
/*		}*/
		var f=this.site.models.File.Create({
			type_id			:type_id,
			file_folder_id	:ff_id,
			orig_filename	:filename,
			ext				:ext,
			user_cr_id		:uid
		});
		f.FetchLocales();
		for (var k in f.l10ns) f.l10ns[k].name=filename;
		if (options.l10ns) for (var k in options.l10ns) if (options.l10ns[k]!="") f.l10ns[k].name=options.l10ns[k];
		f.SaveAll();
		var fid=f.id;
		try {
			var srcfile=path+fid+"."+ext;
			if (options.existingfile) {
				var f=new fs.File(options.existingfile);
				options.is_copy ? f.copy(srcfile) : f.move(srcfile);
				f=new fs.File(srcfile);
			} else {
				var f=new fs.File(srcfile);
				f.open("w");
				f.write(filedata);
				f.close();
			}
			if (downscale_policy && downscale_policy.x!=-1 && type_id==1) {
				var image_type=this.F("Files","gd_image_type_by_ext",ext);
				var image=new gd.Image(image_type,srcfile);
				var ow=image.sx();
				var oh=image.sy();
				if (ow>downscale_policy.x || oh>downscale_policy.y) {
					var tmpfile=path+fid+"-notscaled."+ext;
					f.move(tmpfile);
					this.F("Files","scale_image",{srcfile:tmpfile,dstfile:srcfile,w:downscale_policy.x,h:downscale_policy.y,mo:"",moval:"",exact:"",ext:ext});
					f.remove();
				}
			}
		} catch(e) {
			throw "Can not save file - "+e+", filename: "+path+fid+"."+ext;
		}
		this.F("Files","file_update_stats",fid);
		return fid;
	},

	/**
	 * Function: Files.mkpreview
	 *		Makes preview for some file
	 *		Calls Files.scale_image for actual scaling
	 *		Preview modifiers:
	 *		* ''		- image is scaled to contain in given preview size
	 *		* 'C'		- image is scaled and cropped to to cover in preview size
	 *		* 'T'		- image is trimmed and then scaled
	 *		* 'CT'		- image is trimmed and then scaled and cropped
	 *		* 'R'		- image is made retina-ready
	 *		* 'CR'		- 
	 * Parameters:
	 *		fileheader typeof String	- file name like '/images/myphoto'
	 *		type typeof String			- size of preview, from site.previews[..]
	 *		typemo typeof String		- modifier of preview
	 *		fileext typeof String		- extension like 'jpg'
	 */
	mkpreview: function(filehead,type,typemo,fileext) {
		var size;
		var moval=50;
		var exact=0;
		var typemo_src=typemo;
		var typemo1="";
		var trim=0;
		var retina=0;
		var arr;
		typemo=typemo.replace(/R$/,function() { retina=1; return ""; });
		typemo=typemo.replace(/T$/,function() { trim=1; return ""; });
		typemo=typemo.replace(/C(\d*)$/,function(p,p1) { typemo1="C"; if (p1) { moval=p1*1;exact=1;}; return ""; });

		if (typemo) throw "Files.mkpreview() - cannot parse typemo="+typemo_src+" near "+typemo;
/*
		if (typemo=="T") {
			trim=1;
			typemo1="";
		} else if (typemo=="CT") {
			trim=1;
			typemo1="C";
		} else if (arr=typemo.match(/^C(\d+)(T?)$/)) {
			moval=1*arr[1];
			typemo1="C";
			exact=1;
			if (arr[2]) trim=1;
		}*/
		var basepath=this.site.paths.html;
		var previews=this.site.previews||this.system.previews;
		if (!previews[type]) throw new Error("Preview size "+type+" does not exist in configuration file");
		var xs=previews[type][0];
		var ys=previews[type][1];
		if (retina) {
			xs*=2;
			ys*=2;
		}
		var f=new fs.File(basepath+filehead+"."+fileext);
		if (!f.isFile()) return "not found";
		var config={srcfile:basepath+filehead+"."+fileext,dstfile:basepath+filehead+"-preview"+type+typemo_src+"."+fileext,w:xs,h:ys,mo:typemo1,moval:moval,exact:exact,ext:fileext,trim:trim,upscale:retina};
		var ret=this.F("Files","scale_image",config);
		return undefined;
	},

	get_src_view_file: function(tmplname) {
		var i=0;
		var tmpl;
		var paths=this.views.paths;
		while (i<paths.length && !tmpl) {
			var path=paths[i]+tmplname+".html";
			var f=new fs.File(path);
			if (f.exists()) {	
				f.open('r');
				var contents=f.read().toString("utf-8");
				f.close();
				return contents;
			}
			i++;
		}
		throw "No file found - "+tmplname;
	},

	/**
	 * Function: Files.file_folders_mkdir_rec
	 *		Makes recursively FileFolder
	 *
	 * Parameters:
	 *		user_cr_id typeof Integer		-
	 *		root_ff_id typeof Integer		-
	 *		path typeof Array of String		- smth like ['images','index page','slider']
	 */
	file_folders_mkdir_rec: function(user_cr_id,root_ff_id,path) {
		var ff_id=root_ff_id;
		var ff;
		for (var i=0;i<path.length;i++) {
			ff=this.site.models.FileFolder.Get("get_by_parent_and_name",{parent_id:ff_id,name:path[i]});
			if (!ff) {
				ff=this.site.models.FileFolder.Create();
				ff.user_cr_id=user_cr_id;
				ff.FetchLocales();
				ff.l10ns[1].name=path[i];
				ff.parent_id=ff_id;
				ff.name=path[i];
				ff.SaveAll();
			}
			ff_id=ff.id;
		}
		return ff_id;
	},

	/**
	 * Function: Files.shell_bulk_upload
	 *
	 * Parameters:
	 *		this.fields.root_ff_id typeof Integer
	 *		this.fields.filesystem_folder typeof String
	 *		this.fields.max_files typeof Integer
	 *		this.fields.user_cr_id typeof Integer
	 */
	shell_bulk_upload: function() {
		var t=this;
		if (!this.fields.filesystem_folder) throw "No filesystem_folder parameter given";
		if (!this.fields.user_cr_id) throw "No user_cr_id parameter given";
		var basepath=this.fields.filesystem_folder;
		var user_cr_id=this.fields.user_cr_id;
		var ff_id=this.fields.root_ff_id;
		var max_files;
		if (this.fields.max_files) max_files=this.fields.max_files*1;

		if (!basepath.match(/\/$/)) basepath+="/";

		var fri=this.F("Files","filesystem_recurse_iterator",basepath);
		var entry;
		var dirs=0,files=0;
		var split_path;
		var existing;
		function load_existing(ffid)
		{
			existing={};
			var arr=t.site.models.File.List("list_of_file_folder",{file_folder_id:ffid});
			for (var i=0;i<arr.length;i++) existing[arr[i].name]=arr[i].id;
		}
		load_existing(ff_id);
		while (entry=fri()) {
			system.stdout.writeLine(entry.type+" "+entry.fullname);
			if (entry.type=="directory") {
				// Creating directory
				split_path=entry.fullname.replace(/\/$/,"").split(/\//);
				ff_id=this.F("Files","file_folders_mkdir_rec",user_cr_id,this.fields.root_ff_id,split_path);
				dirs++;
				load_existing(ff_id);
			} else {
				if (existing[entry.name]) {
					system.stdout.writeLine("	skipping existing");
					continue;
				}
				if (!entry.name.match(/\.(jpe?g|png|gif)$/i)) continue;
				if (entry.name.match(/-preview\d/)) continue;
				// Uploading file
				var fileoptions={};
				fileoptions.ff_id			=ff_id;
				fileoptions.uid				=user_cr_id;
				fileoptions.is_copy			=true;
				fileoptions.name			=entry.name;
				fileoptions.l10ns			={ "1" : entry.name};
				fileoptions.existingfile	=basepath+entry.fullname;
				this.F("Files","user_upload_file",fileoptions);
				files++;
			}
			this.site.sql.commit();
//			if (dirs==2) throw this.Dumper({ff_id:ff_id,split_path:split_path});
			if (max_files && files>=max_files) {
				system.stderr.writeLine("Uploaded "+max_files+" files. STOP.");
				throw "STOP";
			}
		}
		system.stderr.writeLine("DONE");
	},

	/**
	 * Function: Files.filesystem_recurse_iterator
	 */
	filesystem_recurse_iterator: function(basepath) {
		if (!basepath.match(/\/$/)) basepath+="/";
		var stack=[];
		function init_dir(path)
		{
			var c={
				path:path,
				dir:new fs.Directory(basepath+path)
			};
			c.directories=c.dir.listDirectories().sort().filter(function(a) { if (a=="." || a=="..") return 0; return 1;});
			c.files=c.dir.listFiles().sort();
			return c;
		}
		var c=init_dir("");
		return function() {
			while (c) {
				if (c.files.length) {
					var f=c.files.pop();
					return {type:"file",fullname:c.path+f,name:f};
				}
				if (c.directories.length) {
					var d=c.directories.pop();
					var fd=c.path+d+"/";
					stack.push(c);
					c=init_dir(fd);
					return {type:"directory",name:d,fullname:fd};
				}
				if (stack.length) c=stack.pop(); else c=undefined;
			}
			return undefined;
		}
	},

	load_site_vars: function() {
		var f=new fs.File(this.site.paths.database+"vars.json");
		if (!this.site.vars && !f.exists()) {
			this.site.vars={};
			return;
		}
		if (this.site.vars && this.site.var_date_mo==f.stat()[9]) return;
		this.site.var_date_mo=f.stat()[9];
		f.open("r");
		this.site.vars=JSON.parse(f.read().toString("utf-8"));
		f.close();
	},

	save_site_vars: function() {
		var f=new fs.File(this.site.paths.database+"vars.json");
		var str=JSON.stringify(this.site.vars);
		f.open("w");
		f.write(str);
		f.close();
	}
},
{
	_type:"controller",
	_config: {
		name:"FilesHelper"
	},
	mk_blur: function() {
		var arr=this.action.replace(/\/$/,"").replace(/\.{2,}/g,"").replace(/[^\w\.\/\-]+/g,"").match(/^(.*\/)([^\/]+)-BL(\d+)\.(jpe?g|png|gif)$/i);
		
		var dst_file=this.site.paths.html+arr[1]+arr[2]+"-BL"+arr[3]+"."+arr[4];
		var src_file=this.site.paths.html+arr[1]+arr[2]+"."+arr[4];
		//throw {arr:arr,dst_file:dst_file,src_file:src_file};
		var f1=new fs.File(src_file);
		if (!f1.exists()) {
			var arr2=arr[2].match(/^(.*)-preview(\d+)(\w*)$/);
//			throw {arr2:arr2,arr_2:arr[2]};
			if (!arr2) throw "No source file found - "+arr[1]+arr[2]+"."+arr[4];
			this.F("Files","mkpreview",arr[1]+arr2[1],arr2[2],arr2[3],arr[4]);
			if (!f1.exists()) throw "No source file found - "+arr[1]+arr[2]+"."+arr[4];
		}
		var p=new proc.Process();
		p.system("convert "+src_file+" -blur "+this.site.blur_sizes[arr[3]]+" "+dst_file);
		var ret;
		try {
			var f=new fs.File(dst_file);
			f.open("r");
			ret=f.read();
			f.close();
		} catch (e) {
			this.jsng_response.render_http_error(this,500,"Error making blur");
			return this.jsng_response.output;
		}
		this.jsng_response.response_headers["Content-type"]=this.F("Files","guess_mime",arr[4]);
		this.action=undefined;
		return ret;
	}
}
];

